如何在 UOJ 评测系统内配置通信题(详细揭秘)

您所在的位置:网站首页 scr pro 2 如何在 UOJ 评测系统内配置通信题(详细揭秘)

如何在 UOJ 评测系统内配置通信题(详细揭秘)

#如何在 UOJ 评测系统内配置通信题(详细揭秘)| 来源: 网络整理| 查看: 265

前言

前几天搬了个远古 IOI 的通信丢到联测去了,vfk 的文档基本啥都没说,然后研究了两天 judger.h 差不多搞清楚怎么在 UOJ 上实现通信评测了。结果数据边数开小被暴力踩了然后赛时改的数据范围于是被 down 爆了。

过两天 gcz 告诉我说有几个人在 U 裙里问怎么配通信题,我也想顺便记录一下配置过程免得下次配通信的时候又走弯路,当然也有可能没有下次了,故作此博客。

正文前的提示

在 UOJ,通信题的配置需要自行编写 judger.cpp。这意味着你需要拥有超级 root,也就是创建网站时第一个注册的账号,仅有这个账号有不使用内置 judger 的权利。

通信题的正确评测姿势

今年省选前集训的时候我和 iotang 已经分别搬了一个通信题了。在 lemon 里 iotang 支持的是一个 grader 同时加多个文件一起编译的评测方式,也是 JOISC 中的本地评测方式。但这样的评测方式有一个致命的缺陷是可以在一个文件里实现两个函数,在另一个文件里啥都不干……如果两个函数可以实现在同一个文件里的话那么显然可以把传入 encode 的信息存全局变量里然后 decode 的时候再拿出来就行了,通信题就没有意义了。

当时竟然没有人这么弄,说明选手素质很高

正确的评测方式应该是弄一个 encode_grader 和一个 decode_grader,先把 encode_grader 和 encode 绑定在一起编译得到 encode 的输出,再把这个输出喂到 decode 和 decode_grader 编译的文件里得到 decode 的输出。

当然在 custom test 中用一个 grader 编译多个文件还是方便一些,可以直接用 judger.h 中的内置评测。

Makefile 的配置 export INCLUDE_PATH CXXFLAGS = -I$(INCLUDE_PATH) -O2 all: chk judger % : %.cpp $(CXX) $(CXXFLAGS) $< -o $@

这个 Makefile 的意义大概是将上传的文件中必要的 cpp 文件进行编译。对于通信题配置,有必要修改的只有 CXXFLAGS 和 all 这两行。

CXXFLAGS 表示的是编译这些文件的开关,比如要开 C++17 就在这一行的最后面加上 -std=c++17。

all 表示的是上传的包中需要编译的文件的名字,不需要包含 cpp 后缀名。举例:如果上传了 chk.cpp,judger.cpp 那么这一行就是 all: chk judger;如果使用内置 checker 那么这一行就是 all: judger;如果有校验器那么这一行就是 all: val judger

你需要注意的是 judger.cpp 总是要编译的。

judger.h 中的重要接口

如果你只想要一个模板 judger 的话可以跳过这一个部分。如果你想要 DIY 的话可以看一下。

这是本文的一个大头。judger.h 包含了评测时用到的一些重要的接口。下文会按照它们在 judger.h 中的出现顺序将有用的一些函数进行简单介绍。如果你不知道 judger.h 在哪里,可在 此处 查看。

下面将一些函数的定义删掉了,把里面的管道交互部分删掉了(因为我不会弄),然后把一些没用的都给删掉了,加了一些函数意义的注释。然后 RS_XXX 是在 uoj_env.h 里面的定义的一个宏,对应的是评测结果是什么,反正就是 RS_AC,RC_WA,RC_OLE,RC_JGF 之类的。

当然下面的还是很长,因为把 test_point 之类的单点测试参考编写都保留了。

#include "uoj_env.h" //这个 .h 包含了所有可能的评测信息如 RS_AC/RS_WA 对应的宏定义。 using namespace std; /*========================== execute ====================== */ //没啥用 /*======================== execute End ==================== */ /*========================= file ====================== */ string file_preview(const string &name, const size_t &len = 100); //读取 name 文件的前 len 个字符,如果超出则在后面会加上三个省略号。 void file_hide_token(const string &name, const string &token); //在 name 文件中尝试读取 token 并把它删掉。如果读取失败则将结果变为 "Unauthorized output"。 /*======================= file End ==================== */ /*====================== parameter ==================== */ struct RunLimit {//时空限制对应的结构体,名字很容易懂。 int time; int real_time; int memory; int output; }; const RunLimit RL_DEFAULT = RunLimit(1, 256, 64); ... //这里有一些默认的限制 struct PointInfo {//测试点结果信息 int num;//测试点编号 int scr;//分数 int ust, usm;//使用的时间和空间 string info, in, out, res;//评测信息/输入文件名/输出文件名/评测结果文件名(又好像) PointInfo(const int &_num, const int &_scr, const int &_ust, const int &_usm, const string &_info, const string &_in, const string &_out, const string &_res) : num(_num), scr(_scr), ust(_ust), usm(_usm), info(_info), in(_in), out(_out), res(_res) { if (info == "default") { if (scr == 0) { info = "Wrong Answer"; } else if (scr == 100) { info = "Accepted"; } else { info = "Acceptable Answer"; } }//如果 info 是 "default",则会按照分数给出对应结果 } }; struct CustomTestInfo {//自定义测试结果信息 int ust, usm;//使用的时间和空间 string info, exp, out;//信息/(可能)输入文件名/输出文件名 CustomTestInfo(const int &_ust, const int &_usm, const string &_info, const string &_exp, const string &_out) : ust(_ust), usm(_usm), info(_info), exp(_exp), out(_out) { } }; struct RunResult {//评测结果信息 int type;//评测结果种类,这里填 RS_XXX int ust, usm; int exit_code;//程序的返回值,如果不为 0 说明 RE 了 static RunResult failed_result(); //构造一个 "Judgement Failed" 的评测状态信息 static RunResult from_file(const string &file_name); //从 file_name 文件中读取评测结果,如果读取失败返回 "Judgement Failed" }; struct RunCheckerResult { //checker 的评测信息 int type;//评测结果种类,这里填 RS_XXX int ust, usm; int scr;//分数 string info;//信息 static RunCheckerResult from_file(const string &file_name, const RunResult &rres); //通过 file_name 的 checker 输出和 rres 中提交程序的评测结果得到合并后的评测结果 static RunCheckerResult failed_result(); //返回一个 "Checker Judgement Failed" 的评测结果 }; struct RunValidatorResult {//校验器结果信息 int type; int ust, usm; bool succeeded;//是否校验成功 string info; static RunValidatorResult failed_result(); //返回一个 "Validator Judgment Failed" 的校验器结果信息 }; struct RunCompilerResult {//评测状态信息 int type; int ust, usm; bool succeeded; string info; static RunCompilerResult failed_result(); //返回一个 "Compile Failed"(不同于 CE)的评测信息 }; int problem_id;//字面意思 string main_path;//主目录 string work_path;//工作目录(放程序的目录) string data_path;//数据存放目录 string result_path;//存放所有评测结果的目录,包括编译、val、chk int tot_time = 0;//最后显示的总时间,可以把这里改了变成最大时间之类的 int max_memory = 0;//最大空间 int tot_score = 0;//总分 ostringstream details_out;//这又是啥 //vector points_info; map config;//存 problem.conf 中所有的 key->val 的映射表 /*==================== parameter End ================== */ /*====================== config set =================== */ void print_config(); //输出 problem.conf 中的内容到 stderr 里 void load_config(const string &filename); //从 filename 里读入 problem.conf string conf_str(const string &key, int num, const string &val); string conf_str(const string &key, const string &val); string conf_str(const string &key); //conf_str(key,(num),(val)):读取 problem.conf 中 key 对应的结果,在带有 num 的情况中 key 会转为 key + "_" + to_string(num) 的形式,如果找不到返回 val,如果没有 val 返回空串 int conf_int(const string &key, const int &val); int conf_int(const string &key, int num, const int &val); int conf_int(const string &key); //conf_int(key,(num),(val)):和上面一样,只是会将结果转成 int 返回,空串返回 0。 /* key = 提交文件名+"_lang" 可以返回该提交文件的编译语言 在提交文件配置里面有一个 name 选项,那就是这里的这个提交文件名。 key = checker 可以得到 checker 的文件名 */ string conf_input_file_name(int num); string conf_output_file_name(int num); //conf_(input/output)_file_name (num):通过 problem.conf 得到第 num 个数据的输入输出文件名。num不知道 0->不是 1-> 是 int validate_input_before_test;//测试前是否先跑val string input_file_name;//输入文件名 string output_file_name;//输出文件名 string answer_file_name;//答案文件名 TestPointConfig() : submit_answer(-1), validate_input_before_test(-1) { } void auto_complete(int num); //如果上面没有设置是否是提交答案/输入输出文件名/是否提前跑一遍val,这里就会自动补全。num 是测试点编号。 }; PointInfo test_point(const string &name, const int &num, TestPointConfig tpc = TestPointConfig()) { //测试一个测试点。其中 name 是文件名,num 是测试点编号,tpc 是测试点信息 tpc.auto_complete(num); if (tpc.validate_input_before_test) { //这里跑了一遍 val RunValidatorResult val_ret = run_validator( tpc.input_file_name, conf_run_limit("validator", 0, RL_VALIDATOR_DEFAULT), conf_str("validator")); if (val_ret.type != RS_AC) { return PointInfo(num, 0, -1, -1, "Validator " + info_str(val_ret.type), file_preview(tpc.input_file_name), "", ""); } else if (!val_ret.succeeded) { return PointInfo(num, 0, -1, -1, "Invalid Input", file_preview(tpc.input_file_name), "", val_ret.info); } } RunResult pro_ret; //运行程序 if (!tpc.submit_answer) { pro_ret = run_submission_program( tpc.input_file_name.c_str(), tpc.output_file_name.c_str(), conf_run_limit(num, RL_DEFAULT), name); if (conf_has("token")) {//读token file_hide_token(tpc.output_file_name, conf_str("token", "")); } if (pro_ret.type != RS_AC) { return PointInfo(num, 0, -1, -1, info_str(pro_ret.type), file_preview(tpc.input_file_name), file_preview(tpc.output_file_name), ""); } } else { pro_ret.type = RS_AC; pro_ret.ust = -1; pro_ret.usm = -1; pro_ret.exit_code = 0; } //用 checker 检查结果 RunCheckerResult chk_ret = run_checker( conf_run_limit("checker", num, RL_CHECKER_DEFAULT), conf_str("checker"), tpc.input_file_name, tpc.output_file_name, tpc.answer_file_name); if (chk_ret.type != RS_AC) { return PointInfo(num, 0, -1, -1, "Checker " + info_str(chk_ret.type), file_preview(tpc.input_file_name), file_preview(tpc.output_file_name), ""); } return PointInfo(num, chk_ret.scr, pro_ret.ust, pro_ret.usm, "default", file_preview(tpc.input_file_name), file_preview(tpc.output_file_name), chk_ret.info); } PointInfo test_hack_point(const string &name, TestPointConfig tpc) { //在 Hack 模式下的评测 tpc.submit_answer = false; tpc.validate_input_before_test = false; tpc.auto_complete(0); RunValidatorResult val_ret = run_validator( tpc.input_file_name, conf_run_limit("validator", 0, RL_VALIDATOR_DEFAULT), conf_str("validator")); if (val_ret.type != RS_AC) { return PointInfo(0, 0, -1, -1, "Validator " + info_str(val_ret.type), file_preview(tpc.input_file_name), "", ""); } else if (!val_ret.succeeded) { return PointInfo(0, 0, -1, -1, "Invalid Input", file_preview(tpc.input_file_name), "", val_ret.info); } RunLimit default_std_run_limit = conf_run_limit(0, RL_DEFAULT); prepare_run_standard_program(); RunProgramConfig rpc; rpc.result_file_name = result_path + "/run_standard_program.txt"; RunResult std_ret = run_submission_program( tpc.input_file_name, tpc.answer_file_name, conf_run_limit("standard", 0, default_std_run_limit), "std", rpc); if (std_ret.type != RS_AC) { return PointInfo(0, 0, -1, -1, "Standard Program " + info_str(std_ret.type), file_preview(tpc.input_file_name), "", ""); } if (conf_has("token")) { file_hide_token(tpc.answer_file_name, conf_str("token", "")); } PointInfo po = test_point(name, 0, tpc); po.scr = po.scr != 100; return po; } CustomTestInfo ordinary_custom_test(const string &name) { //一般的 custom test 测试 RunLimit lim = conf_run_limit(0, RL_DEFAULT); lim.time += 2; string input_file_name = work_path + "/input.txt"; string output_file_name = work_path + "/output.txt"; RunResult pro_ret = run_submission_program( input_file_name, output_file_name, lim, name); if (conf_has("token")) { file_hide_token(output_file_name, conf_str("token", "")); } string info; if (pro_ret.type == RS_AC) { info = "Success"; } else { info = info_str(pro_ret.type); } string exp; if (pro_ret.type == RS_TLE) { exp = "

[time limit: " + vtos(lim.time) + "s]

"; } return CustomTestInfo(pro_ret.ust, pro_ret.usm, info, exp, file_preview(output_file_name, 2048)); } /*====================== test End ================== */ /*======================= conf init =================== */ void main_judger_init(int argc, char **argv); void judger_init(int argc, char **argv);//初始化。程序开始先运行这一句。 /*===================== conf init End ================= */ judger.cpp(可能是)模板

直接丢我自己配 A+B+C 写的 judger.cpp 算了,如果有问题可以直接来喷我。

如果你真的很想知道怎么写,把这个和 judger.h 看完应该是没有问题的了。

具体使用是 programme_name_A 和 programme_name_B 填入对应的两个文件名字(第一个是 encode,第二个是 decode),然后在 require 底下丢 (programme_name_A).h,(programme_name_B).h,compile_(programme_name_A).cpp,compile_(programme_name_B).cpp,grader.cpp,最后这个 grader 就是一次把两个文件编译在一起运行的下发 grader,然后就能用了。

子任务依赖和拓扑排序是从南外 OJ 白嫖来的(

关于编译语言,因为初始的 UOJ 只有 C++ 和 C++11 两个选项,所以除了 C++ 选项以外其他的 C++ 选项一律都是 C++11。当然你也可以在 compile() 和 custom_test() 里自行修改一下。

然后 token 这个东西 encode 和 decode 里都要输出。

没有写 Hack 的原因是自己试了一下发现没法重测和加 extests,有没有老哥教教我 QAQ

里面实现的时候有两个小的变化是:子任务部分分评测是百分比取 \(\min\),有依赖则与前面的依赖分数取 \(\min\);最终的评测时间是所有测试点时间的 \(\max\)。

#include #include"uoj_judger.h" using namespace std; string programme_name_A = "A" , programme_name_B = "B"; vector get_subtask_dependencies(int n){ vector dependencies(n + 1, vector()); for (int t = 1; t minscale; int new_tot_time = 0; PointInfo Judge_point(int id){ TestPointConfig tpc; tpc.auto_complete(id); string tempoutput = work_path + "/temp_output.txt"; RunLimit lim = conf_run_limit(id , RL_DEFAULT); RunResult runA = run_submission_program( tpc.input_file_name , tempoutput , lim, programme_name_A); if(runA.type != RS_AC) return PointInfo(id, 0, -1, -1, "Programme A " + info_str(runA.type) , file_preview(tpc.input_file_name) , file_preview(tempoutput) , ""); if(conf_has("token")) file_hide_token(tempoutput , conf_str("token" , "")); RunResult runB = run_submission_program( tempoutput , tpc.output_file_name, lim, programme_name_B); if(runB.type != RS_AC) return PointInfo(id, 0, -1, -1, "Programme B " + info_str(runB.type) , file_preview(tempoutput) , file_preview(tpc.output_file_name) , ""); if(runA.ust + runB.ust > lim.time * 1000) return PointInfo(id , 0 , -1 , -1 , "Overall Time Limit Exceeded." , "" , "" , ""); if(conf_has("token")) file_hide_token(tpc.output_file_name , conf_str("token" , "")); RunCheckerResult chk_ret = run_checker( conf_run_limit("checker", id, RL_CHECKER_DEFAULT), conf_str("checker"), tpc.input_file_name, tpc.output_file_name, tpc.answer_file_name); if (chk_ret.type != RS_AC) { return PointInfo(id, 0, -1, -1, "Checker " + info_str(chk_ret.type), file_preview(tpc.input_file_name), file_preview(tpc.output_file_name), ""); } return PointInfo(id, chk_ret.scr, runA.ust+runB.ust, max(runA.usm,runB.usm), "default", file_preview(tpc.input_file_name), file_preview(tpc.output_file_name), chk_ret.info); } void Judge_subtask(int id){ vector subtask_testinfo; int mn = 100; for(auto t : subtask_dependencies[id]) mn = min(mn , minscale[t]); if(!mn){minscale[id] = 0; add_subtask_info(id , 0 , "Skipped" , subtask_testinfo); return;} int from = conf_int("subtask_end" , id - 1 , 0) , to = conf_int("subtask_end" , id , 0) , total = conf_int("subtask_score" , id , 0); string statestr = "default"; RunLimit currentlimit = conf_run_limit(id , RL_DEFAULT); for(int i = from + 1 ; i seq = subtask_topo_sort(num , subtask_dependencies); for(auto t : seq) Judge_subtask(t); tot_time = new_tot_time; bool alright = 1; for(int i = 1 ; i


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3